unit frmDVDFilmes;

{
  Copyright 2008 Sean Cross, Source It Ltd
  mailto:sean@sourceitsoftware.com
  Pics Print - The photo printing solution for Windows.
    http://www.picsprint.com
  Rental Property Manager - Rental management made easy
    http://www.sourceitsoftware.com
  Blog
    http://sourceitsoftware.blogspot.com/

  This code can be freely used and distribtued with the following provisions:
  o    Copyright notice remains in place
  o    Credit is provided in the About box, Help file, or website for software using this code
  o    Significant updates should be sent back to sean@sourceitsoftware.com

Other credits
  Andreas Faltinek
}

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  System.Contnrs, Dialogs, GLGui, GLCadencer, GLMaterial, GLScene, GLWindows, GLMirror,
  GLObjects, GLCoordinates, GLCrossPlatform, BaseClasses, GLWin32Viewer, GLGraphics,
  VectorGeometry;

type
  TPageList = class;
  TPage3d = class
  private
    FTransparent: boolean;
    FOwner: TPageList;
    procedure SetTransparent(const Value: boolean);
  public
    Quad: TGLPlane;
    EndPos: TVector;
    constructor Create(AOwner: TPageList);
    function  PageIndex: integer;
    procedure SetAspectRatio(AWidth, AHeight: single);
    property  Transparent: boolean read FTransparent write SetTransparent;
    procedure UpdateTexture(AGraphic: TGraphic; ATargetSize: integer = 1024);
  end;
  TPageList = class(TObjectList)
  private
    function GetItem(Index: Integer): TPage3d;
    procedure SetItem(Index: Integer; const Value: TPage3d);
  public
    function  Add(AObject: TPage3d): Integer;
    function  Remove(AObject: TPage3d): Integer;
    procedure Insert(Index: Integer; AObject: TPage3d);
    property  Items[Index: Integer]: TPage3d read GetItem write SetItem; default;
  end;
  TFRM_DVDFilmes = class(TFrame)
    GLScene1: TGLScene;
    GLCube1: TGLCube;
    GLDummyCube1: TGLDummyCube;
    GLCamera1: TGLCamera;
    GLMaterialLibrary1: TGLMaterialLibrary;
    GLCadencer1: TGLCadencer;
    ViewerFlow: TGLSceneViewer;
    GLMirror1: TGLMirror;
    GLGuiLayout1: TGLGuiLayout;
    GLBaseControl1: TGLBaseControl;
    GLScrollbar1: TGLScrollbar;
    procedure FrameResize(Sender: TObject);
    procedure GLCadencer1Progress(Sender: TObject; const deltaTime,
      newTime: Double);
    procedure ViewerFlowClick(Sender: TObject);
    procedure ViewerFlowMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ViewerFlowMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
  private
    Fpages: TPageList;
    FFirstPageIndex: integer;
    FPagesHorizontal: integer;
    FAnimationPosition: single;
    FAnimationCount: integer;
    FStartTick, FEndTick: cardinal;
    FNewScale: single;
    FMouseX, FMouseY: integer;
    FUseTransparency: boolean;
    FUseMirror: boolean;
    FHotPage: integer;
    FPagesVertical: integer;
    FHorizontalBias: boolean;
    FOnAnimationStart: TNotifyEvent;
    FOnAnimationComplete: TNotifyEvent;
    FOnTransparencyChanged: TNotifyEvent;
    FPixelAspectRatio: single;
    FTextureSize: integer;
    FOnSelecionaPagina: TNotifyEvent;
    procedure Rescale;
    function InternalAddPage(AWidth, AHeight: integer): TPage3d;
    procedure SetFirstPageIndex(const Value: integer);
    procedure SetPagesHorizontal(const Value: integer);

    procedure StartAnimation;
    function  GetLastPageIndex: integer;
    procedure SetUseTransparency(const Value: boolean);
    procedure SetHotPage(const Value: integer);
    procedure SetUseMirror(const Value: boolean);
    function  GetUseMirror: boolean;
    function  GetMirrorTransparency: single;
    procedure SetMirrorTransparency(const Value: single);
    procedure SetPagesVertical(const Value: integer);
    procedure SetMirrorVisibility;
    procedure SetLastPageIndex(const Value: integer);
    procedure SetHorizontalBias(const Value: boolean);
    procedure MakeTransparentCentres(AStartpage, AStep, ARange: integer);
    procedure SetOnAnimationStart(const Value: TNotifyEvent);
    procedure SetOnAnimationComplete(const Value: TNotifyEvent);
    procedure SetOnTransparencyChanged(const Value: TNotifyEvent);
    procedure SetPixelAspectRatio(const Value: single);
    procedure SetTextureSize(const Value: integer);
    procedure SetOnSelecionaPagina(const Value: TNotifyEvent);
  public
    PaginaSelecionada: integer;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    function  AddBlankPage(AWidth: integer = 100; AHeight: integer = 100): TPage3d;
    function  AddPage(AImageIndex: integer): TPage3d; overload;
    function  AddPage(AFilename: string): TPage3d; overload;
//    function  AddPage32(AFilename: string): TPage3d;
//    function  AddPage(ABitmap: TBitmap32): TPage3d; overload;
    function  AddPage(AGraphic: TGraphic): TPage3d; overload;
    procedure RemovePage(Index: integer);
    procedure Clear;
    procedure SetPosition(APage: TPage3d);
    procedure SelectPage(PPage: integer);
    function  PageCount: integer;

    property  FirstPageIndex: integer read FFirstPageIndex write SetFirstPageIndex;
    property  LastPageIndex: integer read GetLastPageIndex write SetLastPageIndex;
    property  PagesHorizontal: integer read FPagesHorizontal write SetPagesHorizontal;
    property  PagesVertical: integer read FPagesVertical write SetPagesVertical;

    property  UseTransparency: boolean read FUseTransparency write SetUseTransparency;
    property  UseMirror: boolean read GetUseMirror write SetUseMirror;
    function  CanUseMirror: boolean;
    property  MirrorTransparency: single read GetMirrorTransparency write SetMirrorTransparency;
    procedure SetAnimationDestinations;
    property  HotPage: integer read FHotPage write SetHotPage;
    property  HorizontalBias: boolean read FHorizontalBias write SetHorizontalBias;
    procedure First;
    procedure Last;
    procedure Next;
    procedure Prior;
    procedure Position(PID: integer);
    procedure Step(ADeltaTimeS: double);
    function  AnimationComplete: boolean;
    function  PageUnderPosition(x, y: integer): integer;
    function  PageUnderMouse: integer;
    function  FPS: extended;

//    function  TargetTextureSize(AWidth, AHeight: Integer): integer; inline;
    function  IndexOf(ASceneObject: TGLSceneObject): integer; overload;
    function  IndexOf(APage: TPage3d): integer; overload;

    procedure SetTransparentRange(AStartPos, AEndPos: integer);
    procedure MakeTransparent(APageIndex: integer);
    procedure MakeTransparentCentre_Top;
    procedure MakeTransparentCentre_Left;
    procedure MakeTransparentCentre_Right;
    procedure MakeTransparentCentre_Bottom;
    procedure TransparencyChanged(Apage: TPage3d);

    function  GetPageRect(APageIndex: integer; var ARect: TRect): boolean;
    property  PixelAspectRatio: single read FPixelAspectRatio write SetPixelAspectRatio;
    property  TextureSize: integer read FTextureSize write SetTextureSize;
  published
    property  OnAnimationStart: TNotifyEvent read FOnAnimationStart write SetOnAnimationStart;
    property  OnAnimationComplete: TNotifyEvent read FOnAnimationComplete write SetOnAnimationComplete;
    property  OnTransparencyChanged: TNotifyEvent read FOnTransparencyChanged write SetOnTransparencyChanged;
    property  OnSelecionaPagina: TNotifyEvent read FOnSelecionaPagina write SetOnSelecionaPagina;
  end;

implementation

{$R *.dfm}

uses Math;

const
  // offsets determine the location of the images
  INITIAL_X_OFFSET = 0.5;
  NEXT_X_OFFSET = 0.25;
  MULTI_OFFSET = 1.1;
  INITIAL_Z_OFFSET = 0.75;
  NEXT_Z_OFFSET = 0.05;

  SCALE_ALLOWANCE_X = 1.2;
  SCALE_ALLOWANCE_Y = 1.4;

  SIDE_ANGLE = -85;

  HOT_ALPHA = 0.3;  // determines how transparent images become when an image behind is highlighted
  MIRROR_ALPHA = 0.90;  // determines how transaparenet the mirror image is 1 = solid, 0 = no mirror

  COORD_X = 0;
  COORD_Y = 1;
  COORD_Z = 2;
  COORD_TURN = 3;

  STEP_SIZE = 0.01;      //
  STEP_INITIAL = 1 - STEP_SIZE;
  TIME_SLICE = 0.001;   // do one step per TIME_SLICE seconds

  SCALE_1_PIXELS = 377; // height of 1*1 cube at scale 1, z = 0


function TargetTextureSize(AWidth, AHeight: Integer; ATargetSize: integer = 1024): integer;
var
  dim: integer;
begin
    // use max texture size of 1024 * 1024 as video card will barf on big images
  dim:= max(AHeight, AWidth);
  result:= ATargetSize div 2;

  while (dim < result) and (result > 1) do
  begin
    result := result div 2;
  end;
  result:= result * 2;  // upsize
end;

function TFRM_DVDFilmes.AddBlankPage(AWidth: integer = 100; AHeight: integer = 100): TPage3d;
begin
  result:= InternalAddPage(AWidth, AHeight);
  result.Quad.Material.MaterialLibrary:= GLMaterialLibrary1;
  result.Quad.Material.LibMaterialName:= GLMaterialLibrary1.Materials[0].Name;
  Rescale;
end;

function TFRM_DVDFilmes.IndexOf(ASceneObject: TGLSceneObject): integer;
begin
  for result := 0 to Fpages.Count - 1 do
    if Fpages[result].Quad = ASceneObject then
      exit;

  result:= -1;
end;

function TFRM_DVDFilmes.IndexOf(APage: TPage3d): integer;
begin
  for result := 0 to Fpages.Count - 1 do
    if Fpages[result] = APage then
      exit;

  result:= -1;
end;

function TFRM_DVDFilmes.InternalAddPage(AWidth, AHeight: integer): TPage3d;
var
  newQuad: TGLPlane;
begin
  result:= TPage3d.Create(Fpages);
  Fpages.Add(result);

  newQuad:= TGLPlane(GLScene1.Objects.AddNewChild(TGLPlane));

  result.Quad:= newQuad;

  result.SetAspectRatio(AWidth, AHeight * FPixelAspectRatio);
  SetPosition(result);

  newQuad.Position.X:= result.EndPos[COORD_x];
  newQuad.Position.Y:= result.EndPos[COORD_Y];
  newQuad.Position.Z:= result.EndPos[COORD_Z];

  if FHorizontalBias then
    newQuad.TurnAngle:=  result.EndPos[COORD_TURN]
  else
    newQuad.PitchAngle:=  result.EndPos[COORD_TURN];

  newQuad.Material.Texture.Disabled:= false;
  newQuad.Material.Texture.MinFilter:= miLinear;
end;

procedure TFRM_DVDFilmes.Last;
begin
  LastPageIndex:= Fpages.Count - 1;
end;

procedure TFRM_DVDFilmes.MakeTransparent(APageIndex: integer);
begin
  if (APageIndex >= 0) and (APageIndex < Fpages.Count) and not Fpages[APageIndex].Transparent then
  begin
    Fpages[APageIndex].Transparent:= true;
    TransparencyChanged(Fpages[APageIndex]);
  end;
end;

procedure TFRM_DVDFilmes.MakeTransparentCentres(AStartpage, AStep, ARange: integer);
begin
  MakeTransparent(AStartpage + AStep * (ARange div 2));
  MakeTransparent(AStartpage + AStep * ((ARange -1 ) div 2));

end;
procedure TFRM_DVDFilmes.MakeTransparentCentre_Bottom;
begin
  MakeTransparentCentres(FFirstPageIndex + FPagesHorizontal * (FPagesVertical - 1), 1, FPagesHorizontal);
end;

procedure TFRM_DVDFilmes.MakeTransparentCentre_Left;
begin
  MakeTransparentCentres(FFirstPageIndex, FPagesHorizontal, FPagesVertical);
//  MakeTransparent(FFirstPageIndex + FPagesHorizontal * (FPagesVertical div 2));
//  MakeTransparent(FFirstPageIndex + FPagesHorizontal * ((FPagesVertical -1 ) div 2));
end;

procedure TFRM_DVDFilmes.MakeTransparentCentre_Right;
begin
  MakeTransparentCentres(FFirstPageIndex + FPagesHorizontal - 1, FPagesHorizontal, FPagesVertical);
end;

procedure TFRM_DVDFilmes.MakeTransparentCentre_Top;
begin
  MakeTransparentCentres(FFirstPageIndex, 1, FPagesHorizontal);
//  MakeTransparent(FFirstPageIndex + 1 * FPagesHorizontal div 2);
//  MakeTransparent(FFirstPageIndex + 1 * ((FPagesHorizontal -1 ) div 2));
end;

procedure TFRM_DVDFilmes.Next;
begin
  FirstPageIndex:= FirstPageIndex + 1;
end;

function TFRM_DVDFilmes.PageCount: integer;
begin
  result:= Fpages.Count;
end;

function TFRM_DVDFilmes.PageUnderMouse: integer;
begin
  result:= PageUnderPosition(FMouseX, FMouseY);
end;

function TFRM_DVDFilmes.PageUnderPosition(x, y: integer): integer;
var
  pick: TGLSceneObject;
begin
  pick := TGLSceneObject(ViewerFlow.Buffer.GetPickedObject(FMouseX, FMouseY));

  if assigned(pick) then
  begin
    result:= IndexOf(pick);
  end
  else result:= -1;
end;

procedure TFRM_DVDFilmes.Position(PID: integer);
begin
  FirstPageIndex:= PID;
end;

procedure TFRM_DVDFilmes.Prior;
begin
  FirstPageIndex:= FirstPageIndex - 1;
end;

function TFRM_DVDFilmes.AddPage(AImageIndex: integer): TPage3d;
begin
  result:= InternalAddPage(10, 10);
  result.Quad.Material.MaterialLibrary:= GLMaterialLibrary1;
  result.Quad.Material.LibMaterialName:= GLMaterialLibrary1.Materials[AImageIndex].Name;

  Rescale;
end;

function TFRM_DVDFilmes.AddPage(AFilename: string): TPage3d;
var pict: TPicture;
begin
  pict:= TPicture.Create;
  try
    pict.LoadFromFile(AFilename);
    result:= AddPage(pict.Graphic);
  finally
    pict.free;
  end;
end;

function TFRM_DVDFilmes.AnimationComplete: boolean;
begin
  result:= FAnimationPosition >= 1;
end;

procedure TFRM_DVDFilmes.StartAnimation;
begin
  FAnimationPosition := 0;
  FAnimationCount:= 0;
   
  if assigned(FOnAnimationStart) then
    FOnAnimationStart(self);

  GLCadencer1.Enabled:= true;
end;

function TFRM_DVDFilmes.CanUseMirror: boolean;
begin
  result:= FUseMirror and (FPagesVertical = 1) and FHorizontalBias;
end;

procedure TFRM_DVDFilmes.Clear;
var page: TPage3d;
begin
  while Fpages.Count > 0 do
  begin
    page:= Fpages[0];
    Fpages.Delete(0);
    page.Quad.Free;
//    page.free;
  end;
end;

constructor TFRM_DVDFilmes.Create(AOwner: TComponent);
begin
  inherited;
  FTextureSize:= 1024;
  FFirstPageIndex:= 0;
  FPagesHorizontal:= 1;
  FPagesVertical:= 1;
  FAnimationPosition:= 1;
  FAnimationCount:= 0;
  FUseTransparency:= true;
  FHorizontalBias:= true;

  FUseMirror:= GLMirror1.Visible;
  Fpages:= TPageList.Create;
  FPixelAspectRatio:= 1;
end;

destructor TFRM_DVDFilmes.Destroy;
begin
  FreeAndNil(Fpages);
  inherited;
end;

procedure TFRM_DVDFilmes.RemovePage(Index: integer);
var page: TPage3d;
begin
  if (Index >= 0) and (Index < Fpages.Count) then
  begin
    page:= Fpages[Index];
    Fpages.Delete(Index);
    page.Quad.Free;
//    page.free;
    SetAnimationDestinations;
  end;
end;

procedure TFRM_DVDFilmes.Rescale;
var
  f: integer;
  xScale, maxYHeight, yScale, xRequired, yRequired, newScale: extended;
begin  
  xRequired:= (FPagesHorizontal * SCALE_ALLOWANCE_X * SCALE_1_PIXELS);
  xScale:= ViewerFlow.Width / xRequired;

  maxYHeight:= 0;

  if (FPagesVertical = 1) and (Fpages.Count > 0) then
  begin
  for f := Max(0, FFirstPageIndex) to LastPageIndex do
    maxYHeight:= Max(maxYHeight, FPages[f].Quad.Height);
  end
  else
    maxYHeight:= 1;

  // images with a height < 1 are not centred , but moved down slightly
  // so can't scale all the way as it will cut off the bottom
  if (maxYHeight <> 1) and CanUseMirror then
    maxYHeight:= (maxYHeight + 1) / 2;

  yRequired:= FPagesVertical * maxYHeight * SCALE_1_PIXELS * SCALE_ALLOWANCE_Y;
  yScale:=    ViewerFlow.Height / yRequired;

  newScale := min(xScale, yScale);

  if (abs(newScale - FNewScale) > 0.01) then
  begin
    FNewScale:= newScale;
    StartAnimation;
  end
  else
  begin
    FNewScale:= newScale;
    // too small a diff to worry about animating
  end;
end;

procedure TFRM_DVDFilmes.First;
begin
  FirstPageIndex:= 0;
end;

function TFRM_DVDFilmes.FPS: extended;
begin
  if (FAnimationCount > 0) and (FEndTick > FStartTick) and (FEndTick > GetTickCount - 1000) then
    result:= FAnimationCount * 1000 / (FEndTick - FStartTick)
  else
    result:= ViewerFlow.FramesPerSecond
end;

procedure TFRM_DVDFilmes.FrameResize(Sender: TObject);
begin
  Rescale;
end;

function TFRM_DVDFilmes.GetLastPageIndex: integer;
begin
  result:= FFirstPageIndex + (FPagesHorizontal * FPagesVertical) - 1;
  if result >= FPages.Count then
    result := FPages.Count - 1;
end;

function TFRM_DVDFilmes.GetMirrorTransparency: single;
begin
  result:= GLMirror1.Material.FrontProperties.Diffuse.Alpha;
end;

function TFRM_DVDFilmes.GetPageRect(APageIndex: integer;
  var ARect: TRect): boolean;
var
  AbsRect: TRect;
  quad: TPage3d;
begin
  if (APageIndex >= 0) and (APageIndex < Fpages.Count) then
  begin
    quad:= Fpages[ApageIndex]; //.GetPosition(Rect);

    AbsRect:= quad.Quad.ScreenRect(nil);
    ARect.left:= AbsRect.left; // + CoverFlow.Left;
    ARect.Right:= AbsRect.Right; // + CoverFlow.Left;
    ARect.Top:= ViewerFlow.Height - AbsRect.Bottom;
    ARect.Bottom:= ViewerFlow.height - AbsRect.Top;
    result:= true;
  end
  else
    result:= false;
end;

function TFRM_DVDFilmes.GetUseMirror: boolean;
begin
  result:= FUseMirror;
end;

procedure TFRM_DVDFilmes.GLCadencer1Progress(Sender: TObject; const deltaTime,
  newTime: Double);
begin
  Step(deltaTime);
  if AnimationComplete then
    GLCadencer1.Enabled:= false;
end;

procedure TFRM_DVDFilmes.ViewerFlowClick(Sender: TObject);
var
  cubeIndex: integer;
begin
  cubeIndex:= PageUnderMouse;
  if cubeIndex >= 0 then
  begin
    if cubeIndex > LastPageIndex then
      LastPageIndex:= cubeIndex
    else
      FirstPageIndex:= cubeIndex;
  end;
end;

procedure TFRM_DVDFilmes.ViewerFlowMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  FMouseX:= X;
  FMouseY:= Y;
end;

procedure TFRM_DVDFilmes.ViewerFlowMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
begin
  FMouseX:= X;
  FMouseY:= Y;

  if FUseTransparency then
    HotPage:= PageUnderMouse;
end;

procedure TFRM_DVDFilmes.SelectPage(PPage: integer);
begin
  SetPosition(Fpages[PPage]);
end;

procedure TFRM_DVDFilmes.SetAnimationDestinations;
var f: integer;
begin
  StartAnimation;

  for f := 0 to FPages.Count - 1 do
    SetPosition(Fpages[f]);

  Rescale;
end;

procedure TFRM_DVDFilmes.SetFirstPageIndex(const Value: integer);
begin
  if FFirstPageIndex<> Value then
  begin
    FFirstPageIndex:= Value;
    HotPage:= -1;
    SetAnimationDestinations;
  end;
end;

procedure TFRM_DVDFilmes.SetHorizontalBias(const Value: boolean);
begin
  if Value <> FHorizontalBias then
  begin
    FHorizontalBias:= Value;
    SetMirrorVisibility;
    SetAnimationDestinations;
  end;
end;

procedure TFRM_DVDFilmes.SetHotPage(const Value: integer);
begin
  // make transparent all pages overlapping this page
  // where the central block is > 1*1, only do the inner pages of the edge, not the complete edge
  if FHotPage <> Value then
  begin
    if FUseTransparency then
    begin
      if Value < 0 then
        SetTransparentRange(-1, -1)
      else
      begin
        if Value < FFirstPageIndex then
        begin
          // before central block
          SetTransparentRange(Value + 1, FirstPageIndex - 1);
          if FHorizontalBias then
            MakeTransparentCentre_Left
          else
            MakeTransparentCentre_Top;
        end
        else if Value > LastPageIndex then
        begin
          // after central block
          SetTransparentRange(LastPageIndex + 1, value - 1);
          if FHorizontalBias then
            MakeTransparentCentre_Right
          else
            MakeTransparentCentre_Bottom;
        end
        else
          SetTransparentRange(-1, -1);  // in central block
      end;
    end;

    FHotPage := Value;
  end;
end;

procedure TFRM_DVDFilmes.SetLastPageIndex(const Value: integer);
var newIndex: integer;
begin
  newIndex:= value - FPagesHorizontal * FPagesVertical + 1;
  if newIndex < 0 then
    newIndex:= 0;

  FirstPageIndex:= newIndex;
end;

procedure TFRM_DVDFilmes.SetMirrorTransparency(const Value: single);
begin
  if (Value >= 0) and (Value <= 1) then
    GLMirror1.Material.FrontProperties.Diffuse.Alpha:= Value;
end;

procedure TFRM_DVDFilmes.SetMirrorVisibility;
begin
  GLMirror1.Visible:= CanUseMirror;
end;

procedure TFRM_DVDFilmes.SetOnAnimationComplete(const Value: TNotifyEvent);
begin
  FOnAnimationComplete := Value;
end;

procedure TFRM_DVDFilmes.SetOnAnimationStart(const Value: TNotifyEvent);
begin
  FOnAnimationStart := Value;
end;

procedure TFRM_DVDFilmes.SetOnSelecionaPagina(const Value: TNotifyEvent);
begin
  FOnSelecionaPagina := Value;
end;

procedure TFRM_DVDFilmes.SetOnTransparencyChanged(const Value: TNotifyEvent);
begin
  FOnTransparencyChanged := Value;
end;

procedure TFRM_DVDFilmes.SetPagesHorizontal(const Value: integer);
begin
  if (value > 0) and (value <> FPagesHorizontal) then
  begin
    FPagesHorizontal := Value;
    HotPage:= -1;
    SetAnimationDestinations;
  end;
end;

procedure TFRM_DVDFilmes.SetPagesVertical(const Value: integer);
begin
  if (value > 0) and (value <> FPagesVertical) then
  begin
    FPagesVertical := Value;
    HotPage:= -1;
    SetMirrorVisibility;
    SetAnimationDestinations;
  end;
end;

procedure TFRM_DVDFilmes.SetPixelAspectRatio(const Value: single);
begin
  if value > 0 then
    FPixelAspectRatio := Value;
end;

procedure TFRM_DVDFilmes.SetPosition(APage: TPage3d);
var
  zOffset: single;
  direction, xOffset, multiXPageOffset, multiYPageOffset, angle: single;
  distance: integer;
  maxDim: integer;
begin
  multiXPageOffset:= (FPagesHorizontal - 1) / 2 * MULTI_OFFSET;
  multiYPageOffset:= (FPagesVertical - 1) / 2 * MULTI_OFFSET;
  

  APage.EndPos[COORD_Y]:= 0;

  // in centre block
  if (APage.pageIndex >= FFirstPageIndex) and (APage.pageIndex <= LastPageIndex) then
  begin
    distance:= APage.pageIndex - FFirstPageIndex;
    APage.EndPos[COORD_X]:= (distance mod FPagesHorizontal) * MULTI_OFFSET - multiXPageOffset;
    APage.EndPos[COORD_Y]:= multiYPageOffset - ((distance div FPagesHorizontal) * MULTI_OFFSET);
    APage.EndPos[COORD_Z]:= 0;
    APage.EndPos[COORD_TURN]:= 0;

    PaginaSelecionada := APage.pageIndex;
    if Assigned(FOnSelecionaPagina) then
      FOnSelecionaPagina(Self);
  end
  else
  begin
    maxDim:= max(FPagesHorizontal, FPagesVertical);
    angle:= SIDE_ANGLE - maxDim * 2;
    if (APage.pageIndex < FFirstPageIndex) then
    begin
      // left side
      direction:= -1;
      distance:= FFirstPageIndex - APage.pageIndex;
    end
    else
    begin
      // right side
      direction:=1;
      distance:= APage.pageIndex - LastPageIndex;
    end;

    multiXPageOffset:= multiXPageOffset * 5 /4;

    if HorizontalBias then
    begin
      xOffset:= multiXPageOffset + INITIAL_X_OFFSET + distance * NEXT_X_OFFSET;
      APage.EndPos[COORD_X]:= xOffset * direction;
      APage.EndPos[COORD_Y]:= 0;
      APage.EndPos[COORD_TURN]:= (direction) * angle;
    end
    else
    begin
      xOffset:= multiYPageOffset + INITIAL_X_OFFSET + distance * NEXT_X_OFFSET;
      
      APage.EndPos[COORD_X]:= 0;
      APage.EndPos[COORD_Y]:= -xOffset * direction;
      APage.EndPos[COORD_TURN]:= -direction * angle;
    end;

    zOffset := (INITIAL_Z_OFFSET + distance * NEXT_Z_OFFSET) / maxDim;
    APage.EndPos[COORD_Z]:= -zOffset;

  end;

  //    Quad.Position.Y:= (Quad.Height / 2) - 0.5;
  if CanUseMirror then
    APage.EndPos[COORD_Y]:= APage.EndPos[COORD_Y] + (APage.Quad.Height / 2) - 0.5;
end;

procedure TFRM_DVDFilmes.SetTextureSize(const Value: integer);
var newSize: integer;
begin
  if (value > 0) and (FTextureSize <> Value) then
  begin
    // must be a power of 2
    newSize:= 32;
    while (newSize < value) do
    begin
      newSize:= newSize * 2;
    end;
    FTextureSize := newSize;
  end;
end;

procedure TFRM_DVDFilmes.SetTransparentRange(AStartPos, AEndPos: integer);
var
  f: integer;
  setTransparent: boolean;
begin
  // TODO : set middle images trans parent as well
  for f := 0 to FPages.Count - 1 do
  begin
    setTransparent:= FUseTransparency and (f >= AStartPos) and (f <= AEndPos);
    if Fpages[f].Transparent<> setTransparent then
    begin
      Fpages[f].Transparent:= setTransparent;
      TransparencyChanged(Fpages[f]);
    end;
  end;
  ViewerFlow.Invalidate;
end;

procedure TFRM_DVDFilmes.SetUseMirror(const Value: boolean);
begin
  if Value <> FUseMirror then
  begin
    FUseMirror:= Value;
    SetMirrorVisibility;
    SetAnimationDestinations;
  end;
end;

procedure TFRM_DVDFilmes.SetUseTransparency(const Value: boolean);
begin
  if FUseTransparency <> Value then
  begin
    FUseTransparency := Value;
    HotPage:= -1;  // clear all transpararency, fix up on next mouse movement
  end;
end;

procedure TFRM_DVDFilmes.Step(ADeltaTimeS: double);
var
  f: integer;
  page: TPage3d;
  wasComplete: boolean;
  function DoStep(AStartPos, AEndPos: double): double;
  begin
    if AnimationComplete then
      result:= AEndPos
    else
      result:= AStartPos * STEP_INITIAL + STEP_SIZE * AEndPos;
  end;
begin
  if FAnimationCount = 0 then
  begin
    FStartTick:= GetTickCount;
  end;

  wasComplete:= AnimationComplete;

  inc(FAnimationCount);
  FEndTick:= GetTickCount;

  repeat
    FAnimationPosition:= DoStep(FAnimationPosition, 1.0);
    if FAnimationPosition >= 0.997 then
      FAnimationPosition:= 1;

    GLCamera1.SceneScale:= DoStep(GLCamera1.SceneScale, FNewScale);

    for f := 0 to FPages.Count - 1 do
    begin
      page:= fpages[f];
      page.Quad.Position.X:= DoStep(page.Quad.Position.X, page.EndPos[COORD_X]);
      page.Quad.Position.Y:= DoStep(page.Quad.Position.Y, page.EndPos[COORD_Y]);
      page.Quad.Position.Z:= DoStep(page.Quad.Position.Z, page.EndPos[COORD_Z]);

      if HorizontalBias then
      begin
        page.Quad.PitchAngle:= 0;
        page.Quad.TurnAngle:=  DoStep(page.Quad.TurnAngle,  page.EndPos[COORD_TURN]);
      end
      else
      begin
        page.Quad.TurnAngle:= 0;
        page.Quad.PitchAngle:= DoStep(page.Quad.PitchAngle,  page.EndPos[COORD_TURN]);
      end;
    end;
    ADeltaTimeS:= ADeltaTimeS - TIME_SLICE;
  until (ADeltaTimeS <= 0) or AnimationComplete;

  if not wasComplete and AnimationComplete and Assigned(FOnAnimationComplete) then
  begin
    ViewerFlow.Repaint;
    FOnAnimationComplete(self);
  end;

end;

procedure TFRM_DVDFilmes.TransparencyChanged(Apage: TPage3d);
begin
  if assigned(FOnTransparencyChanged) then
    FOnTransparencyChanged(Apage);
end;

function TFRM_DVDFilmes.AddPage(AGraphic: TGraphic): TPage3d;
begin
  if (AGraphic.Width > 0) and (AGraphic.Height > 0) then
  begin
    result:= InternalAddPage(AGraphic.Width, AGraphic.Height);
    result.UpdateTexture(AGraphic, FTextureSize);
    Rescale;
  end
  else
    result:= nil;
end;

//function TFrameCoverFlow.AddPage(ABitmap: TBitmap32): TPage3d;
//var
//  dim, textureSize: integer;
//  bitmap: TBitmap32;
////  resampler: TNearestResampler;
//begin
//  if (ABitmap.Width > 0) and (ABitmap.Height > 0) then
//  begin
//    result:= InternalAddPage(ABitmap.Width, ABitmap.Height);
//
//    textureSize:= TargetTextureSize(ABitmap.Width, ABitmap.Height);
//
//    // todo : better quality resize
//    if (ABitmap.Height= textureSize) and (ABitmap.Width = textureSize)  then
//     result.Quad.Material.Texture.Image.Assign(ABitmap)
//    else
//    begin
//      bitmap:= TBitmap32.Create;
//      try
//        bitmap.Width:= textureSize;
//        bitmap.Height:= textureSize;
//
//        bitmap.ResamplerClassName := 'TNearestResampler';
//        bitmap.Draw(bitmap.BoundsRect, ABitmap.BoundsRect, ABitmap);
//
//        result.Quad.Material.Texture.Image.GetBitmap32(0).Assign(bitmap);
//
//      finally
//        bitmap.Free;
//      end;
//    end;
//    Rescale;
//  end
//  else
//    result:= nil;
//end;

//function TFrameCoverFlow.AddPage32(AFilename: string): TPage3d;
//var bm: TBitmap32;
//begin
//  bm:= TBitmap32.Create;
//  try
//    bm.LoadFromFile(AFilename);
//    result:= AddPage(bm);
//  finally
//    bm.free;
//  end;
//end;

{ TPage3d }

constructor TPage3d.Create(AOwner: TPageList);
begin
  inherited Create;
  FOwner:= AOwner;
end;

function TPage3d.PageIndex: integer;
begin
  Assert(assigned(FOwner));
  result:= FOwner.IndexOf(self);
end;

procedure TPage3d.SetAspectRatio(AWidth, AHeight: single);
begin
  Quad.Width:= 1;
  Quad.Height:= 1;
  Quad.Position.Y:= 0;

  if AHeight > AWidth then // portrait
    Quad.Width:= AWidth / AHeight
  else if AHeight < AWidth then // landscape
  begin
    Quad.Height:= AHeight /AWidth;
  end;
end;

procedure TPage3d.SetTransparent(const Value: boolean);
begin
  if FTransparent <> Value then
  begin
    FTransparent := Value;
    if Value then
    begin
      Quad.Material.FrontProperties.Diffuse.Alpha:= HOT_ALPHA;
      Quad.Material.BlendingMode:= bmTransparency
    end
    else
    begin
      Quad.Material.FrontProperties.Diffuse.Alpha:= 1;
      Quad.Material.BlendingMode:= bmOpaque;
    end;
  end;
end;

procedure TPage3d.UpdateTexture(AGraphic: TGraphic; ATargetSize: integer = 1024);
var
  textureSize: integer;
  bitmap: TBitmap;
begin
  if (AGraphic.Width > 0) and (AGraphic.Height > 0) then
  begin
    Quad.Material.MaterialLibrary:= nil;
    Quad.Material.LibMaterialName:= '';

    textureSize:= TargetTextureSize(AGraphic.Width, AGraphic.Height, ATargetSize);

    // todo : better quality resize
    if (AGraphic.Height= textureSize) and (AGraphic.Width = textureSize)  then
     Quad.Material.Texture.Image.Assign(AGraphic)
    else
    begin
      bitmap:= TBitmap.Create;
      try
        bitmap.Width:= textureSize;
        bitmap.Height:= textureSize;

        bitmap.Canvas.StretchDraw(bitmap.Canvas.ClipRect, AGraphic);

        Quad.Material.Texture.Image.Assign(bitmap);
      finally
        bitmap.Free;
      end;
    end;
  end;
end;

{ TPageList }

function TPageList.Add(AObject: TPage3d): Integer;
begin
  result:= inherited Add(AObject);
end;

function TPageList.GetItem(Index: Integer): TPage3d;
begin
  result:= TPage3d(inherited GetItem(Index));
end;

procedure TPageList.Insert(Index: Integer; AObject: TPage3d);
begin
  inherited Insert(Index, AObject);
end;

function TPageList.Remove(AObject: TPage3d): Integer;
begin
  result:= (inherited Remove(AObject));
end;

procedure TPageList.SetItem(Index: Integer; const Value: TPage3d);
begin
  inherited SetItem(Index, Value);
end;


end.
